/**
 *
 */
package com.browseengine.bobo.util;

import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.log4j.Logger;

public class MemoryManager<T> implements MemoryManagerAdminMBean {
  private static final Logger log = Logger.getLogger(MemoryManager.class.getName());
  private static final int[] sizetable;
  private final AtomicLong _hits = new AtomicLong(0);
  private final AtomicLong _miss = new AtomicLong(0);
  static {
    int initsize = 1024;
    double ratio = 1.3;
    int l = (int) (Math.log(Integer.MAX_VALUE / initsize) / Math.log(ratio)) + 1;
    sizetable = new int[l];
    sizetable[0] = initsize;
    for (int i = 1; i < sizetable.length; i++) {
      sizetable[i] = (int) (sizetable[i - 1] * ratio);
    }
  }
  private final ConcurrentHashMap<Integer, ConcurrentLinkedQueue<WeakReference<T>>> _sizeMap = new ConcurrentHashMap<Integer, ConcurrentLinkedQueue<WeakReference<T>>>();
  private volatile ConcurrentLinkedQueue<T> _releaseQueue = new ConcurrentLinkedQueue<T>();
  private volatile ConcurrentLinkedQueue<T> _releaseQueueb = new ConcurrentLinkedQueue<T>();
  private final AtomicInteger _releaseQueueSize = new AtomicInteger(0);
  private final Initializer<T> _initializer;
  private final Thread _cleanThread;

  public MemoryManager(Initializer<T> initializer) {
    this._initializer = initializer;
    _cleanThread = new Thread(new Runnable() {

      @Override
      public void run() {
        T buf = null;
        while (true) {
          synchronized (MemoryManager.this) {
            try {
              MemoryManager.this.wait(10);
            } catch (InterruptedException e) {
              log.error(e);
            }
          }
          ConcurrentLinkedQueue<T> t = _releaseQueue;
          _releaseQueue = _releaseQueueb;
          _releaseQueueb = t;
          while ((buf = _releaseQueueb.poll()) != null) {
            ConcurrentLinkedQueue<WeakReference<T>> queue = _sizeMap.get(_initializer.size(buf));
            // buf is wrapped in WeakReference. this allows GC to reclaim the buffer memory
            _initializer.init(buf);// pre-initializing the buffer in parallel so we save time when
                                   // it is requested later.
            queue.offer(new WeakReference<T>(buf));
            _releaseQueueSize.decrementAndGet();
          }
          buf = null;
        }
      }
    });
    _cleanThread.setDaemon(true);
    _cleanThread.start();
  }

  public MemoryManagerAdminMBean getAdminMBean() {
    return this;
  }

  @Override
  public long getNumCacheMisses() {
    return _miss.get();
  }

  @Override
  public long getNumCacheHits() {
    return _hits.get();
  }

  @Override
  public double getHitRate() {
    long miss = _miss.get();
    long hit = _hits.get();
    return (double) hit / (double) (hit + miss);
  }

  /**
   * @return an initialized instance of type T. The size of the instance may not be the same as the requested size.
   */
  public T get(int reqsize) {
    return _initializer.newInstance(reqsize);
    // long t0 = System.currentTimeMillis();
    // int size = reqsize;
    // for(int i = 0; i<sizetable.length; i++)
    // {
    // if (sizetable[i] >= reqsize)
    // {
    // size = sizetable[i];
    // break;
    // }
    // }
    // ConcurrentLinkedQueue<WeakReference<T>> queue = _sizeMap.get(size);
    // if (queue==null)
    // {
    // queue = new ConcurrentLinkedQueue<WeakReference<T>>();
    // _sizeMap.putIfAbsent(size, queue);
    // queue = _sizeMap.get(size);
    // }
    // while(true)
    // {
    // WeakReference<T> ref = (WeakReference<T>) queue.poll();
    // if(ref != null)
    // {
    // T buf = ref.get();
    // if(buf != null)
    // {
    // _hits.incrementAndGet();
    // return buf;
    // }
    // }
    // else
    // {
    // T ret = _initializer.newInstance(size);
    // _miss.incrementAndGet();
    // long hit = _hits.get();
    // if (hit > Long.MAX_VALUE/2)
    // {
    // _hits.set(0);
    // _miss.set(0);
    // }
    // return ret;
    // }
    // }
  }

  /**
   * return the instance to the manager after use
   * @param buf
   */
  public void release(T buf) {
    // if (_releaseQueueSize.get()>8000)
    // {
    // log.info("release queue full");
    // synchronized(MemoryManager.this)
    // {
    // MemoryManager.this.notifyAll();
    // }
    // return;
    // }
    // if(buf != null)
    // {
    // _releaseQueue.offer(buf);
    // _releaseQueueSize.incrementAndGet();
    // synchronized(MemoryManager.this)
    // {
    // MemoryManager.this.notifyAll();
    // }
    // }
  }

  public static interface Initializer<E> {
    public E newInstance(int size);

    public int size(E buf);

    public void init(E buf);
  }
}
